use std::mem::size_of;
use std::num::Wrapping;

pub trait ToPrimitive {
  #[inline]
  fn to_isize(&self) -> Option<isize> {
    self.to_i64().as_ref().and_then(ToPrimitive::to_isize)
  }

  #[inline]
  fn to_i8(&self) -> Option<i8> {
    self.to_i64().as_ref().and_then(ToPrimitive::to_i8)
  }

  #[inline]
  fn to_i16(&self) -> Option<i16> {
    self.to_i64().as_ref().and_then(ToPrimitive::to_i16)
  }

  #[inline]
  fn to_i32(&self) -> Option<i32> {
    self.to_i64().as_ref().and_then(ToPrimitive::to_i32)
  }

  fn to_i64(&self) -> Option<i64>;

  #[inline]
  fn to_i128(&self) -> Option<i128> {
    self.to_i64().map(From::from)
  }

  #[inline]
  fn to_usize(&self) -> Option<usize> {
    self.to_u64().as_ref().and_then(ToPrimitive::to_usize)
  }

  #[inline]
  fn to_u8(&self) -> Option<u8> {
    self.to_u64().as_ref().and_then(ToPrimitive::to_u8)
  }

  #[inline]
  fn to_u16(&self) -> Option<u16> {
    self.to_u64().as_ref().and_then(ToPrimitive::to_u16)
  }

  #[inline]
  fn to_u32(&self) -> Option<u32> {
    self.to_u64().as_ref().and_then(ToPrimitive::to_u32)
  }

  fn to_u64(&self) -> Option<u64>;

  #[inline]
  fn to_u128(&self) -> Option<u128> {
    self.to_u64().map(From::from)
  }
}

macro_rules! impl_to_primitive_int_to_int {
  ($SrcT:ident : $( fn $method:ident -> $DstT:ident; )*) => {$(
    #[inline]
    fn $method(&self) -> Option<$DstT> {
      let min = $DstT::MIN as $SrcT;
      let max = $DstT::MAX as $SrcT;
      if size_of::<$SrcT>() <= size_of::<$DstT>() || (min <= *self && *self <= max) {
        Some(*self as $DstT)
      } else {
        None
      }
    }
  )*}
}

macro_rules! impl_to_primitive_int_to_uint {
  ($SrcT:ident : $( fn $method:ident -> $DstT:ident; )*) => {$(
    #[inline]
    fn $method(&self) -> Option<$DstT> {
      let max = $DstT::MAX as $SrcT;
      if 0 <= *self && (size_of::<$SrcT>() <= size_of::<$DstT>() || *self <= max) {
        Some(*self as $DstT)
      } else {
        None
      }
    }
  )*}
}

macro_rules! impl_to_primitive_int {
  ($T:ident) => {
    impl ToPrimitive for $T {
      impl_to_primitive_int_to_int! { $T:
        fn to_isize -> isize;
        fn to_i8 -> i8;
        fn to_i16 -> i16;
        fn to_i32 -> i32;
        fn to_i64 -> i64;
        fn to_i128 -> i128;
      }

      impl_to_primitive_int_to_uint! { $T:
        fn to_usize -> usize;
        fn to_u8 -> u8;
        fn to_u16 -> u16;
        fn to_u32 -> u32;
        fn to_u64 -> u64;
        fn to_u128 -> u128;
      }
    }
  };
}

impl_to_primitive_int!(isize);
impl_to_primitive_int!(i8);
impl_to_primitive_int!(i16);
impl_to_primitive_int!(i32);
impl_to_primitive_int!(i64);
impl_to_primitive_int!(i128);

macro_rules! impl_to_primitive_uint_to_int {
  ($SrcT:ident : $( fn $method:ident -> $DstT:ident; )*) => {$(
    #[inline]
    fn $method(&self) -> Option<$DstT> {
      let max = $DstT::MAX as $SrcT;
      if size_of::<$SrcT>() < size_of::<$DstT>() || *self <= max {
        Some(*self as $DstT)
      } else {
        None
      }
    }
  )*}
}

macro_rules! impl_to_primitive_uint_to_uint {
  ($SrcT:ident : $( fn $method:ident -> $DstT:ident; )*) => {$(
    #[inline]
    fn $method(&self) -> Option<$DstT> {
      let max = $DstT::MAX as $SrcT;
      if size_of::<$SrcT>() <= size_of::<$DstT>() || *self <= max {
        Some(*self as $DstT)
      } else {
        None
      }
    }
  )*}
}

macro_rules! impl_to_primitive_uint {
  ($T:ident) => {
    impl ToPrimitive for $T {
      impl_to_primitive_uint_to_int! { $T:
        fn to_isize -> isize;
        fn to_i8 -> i8;
        fn to_i16 -> i16;
        fn to_i32 -> i32;
        fn to_i64 -> i64;
        fn to_i128 -> i128;
      }

      impl_to_primitive_uint_to_uint! { $T:
        fn to_usize -> usize;
        fn to_u8 -> u8;
        fn to_u16 -> u16;
        fn to_u32 -> u32;
        fn to_u64 -> u64;
        fn to_u128 -> u128;
      }
    }
  };
}

impl_to_primitive_uint!(usize);
impl_to_primitive_uint!(u8);
impl_to_primitive_uint!(u16);
impl_to_primitive_uint!(u32);
impl_to_primitive_uint!(u64);
impl_to_primitive_uint!(u128);

pub trait NumCast: Sized + ToPrimitive {
  fn from<T: ToPrimitive>(n: T) -> Option<Self>;
}

#[inline]
pub fn cast<T: NumCast, U: NumCast>(n: T) -> Option<U> {
  NumCast::from(n)
}

macro_rules! impl_num_cast {
  ($T:ty, $conv:ident) => {
    impl NumCast for $T {
      #[inline]
      #[allow(deprecated)]
      fn from<N: ToPrimitive>(n: N) -> Option<$T> {
        n.$conv()
      }
    }
  };
}

impl_num_cast!(u8, to_u8);
impl_num_cast!(u16, to_u16);
impl_num_cast!(u32, to_u32);
impl_num_cast!(u64, to_u64);
impl_num_cast!(u128, to_u128);
impl_num_cast!(usize, to_usize);
impl_num_cast!(i8, to_i8);
impl_num_cast!(i16, to_i16);
impl_num_cast!(i32, to_i32);
impl_num_cast!(i64, to_i64);
impl_num_cast!(i128, to_i128);
impl_num_cast!(isize, to_isize);

impl<T: NumCast> NumCast for Wrapping<T> {
  fn from<U: ToPrimitive>(n: U) -> Option<Self> {
    T::from(n).map(Wrapping)
  }
}

macro_rules! impl_to_primitive_wrapping {
  ($( fn $method:ident -> $i:ident ; )*) => {$(
    #[inline]
    fn $method(&self) -> Option<$i> {
      (self.0).$method()
    }
  )*}
}

impl<T: ToPrimitive> ToPrimitive for Wrapping<T> {
  impl_to_primitive_wrapping! {
    fn to_isize -> isize;
    fn to_i8 -> i8;
    fn to_i16 -> i16;
    fn to_i32 -> i32;
    fn to_i64 -> i64;
    fn to_i128 -> i128;

    fn to_usize -> usize;
    fn to_u8 -> u8;
    fn to_u16 -> u16;
    fn to_u32 -> u32;
    fn to_u64 -> u64;
    fn to_u128 -> u128;
  }
}
